﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Swift;
using Swift.Math;
using Swift.AStar;

namespace SCM
{

    /// <summary>
    /// 地图格子，表达占位信息
    /// </summary>
    public class MapGrid
    {
        int[,] grid = null;
        int w;
        int h;
        int maxWH;

        public int GridSizeScaler { get; private set; }

        int t(Fix64 n) {
            if (n == 0)
                return 0;
            else if (n > 0)
                return (int)(n / GridSizeScaler).Clamp(1, int.MaxValue);
            else
                return (int)(n / GridSizeScaler).Clamp(int.MinValue, -1);
        }

        Fix64 _t(int n) { return (Fix64)(n * GridSizeScaler); }

        public MapGrid(Vec2 mapSize, int gridSizeScaler)
        {
            GridSizeScaler = gridSizeScaler;
            w = t(mapSize.x) + 1;
            h = t(mapSize.y) + 1;
            maxWH = w > h ? w : h;
            grid = new int[w, h];
        }

        // 寻找离指定位置最近的一个可容纳指定半径单位的点，从离中心 fromDistance 的距离开始找
        public bool FindNearestSpareSpace(Vec2 center, Fix64 radius, Fix64 fromDistance, out Vec2 pt)
        {
            var ptFound = Vec2.Zero;
            bool found = false;
            var cx = t(center.x);
            var cy = t(center.y);
            var r = t(radius);
            var fd = t(fromDistance);
            FC.SquareFor2(cx, cy, fd, maxWH, (x, y) =>
            {
                if (x < 0 || x >= w || y < 0 || y >= h)
                    return;

                if (_CheckSpareSpace(x, y, r))
                {
                    ptFound = new Vec2(x, y);
                    found = true;
                }
            }, FC.SquareForSeq.Default, () => !found);

            pt = new Vec2(ptFound.x * GridSizeScaler, ptFound.y * GridSizeScaler);
            return found;
        }

        public bool CheckSpareSpace(Vec2 center, Fix64 radius) { return CheckSpareSpace(center.x, center.y, radius); }
        public bool CheckSpareSpace(Fix64 cx, Fix64 cy, Fix64 radius)
        {
            return _CheckSpareSpace(t(cx), t(cy), t(radius));
        }

        public int GetInGrid(Fix64 cx, Fix64 cy)
        {
            return _GetInGrid(t(cx), t(cy));
        }

        public void SetGrid(Fix64 cx, Fix64 cy, Fix64 radius)
        {
            _SetGrid(t(cx), t(cy), t(radius));
        }

        public void UnsetGrid(Fix64 cx, Fix64 cy, Fix64 radius)
        {
            _UnsetGrid(t(cx), t(cy), t(radius));
        }

        public void SetInGrid(Fix64 cx, Fix64 cy, int v)
        {
            _SetInGrid(t(cx), t(cy), v);
        }

        public void SetLine(Fix64 sx, Fix64 sy, Fix64 ex, Fix64 ey)
        {
            _SetLine(t(sx), t(sy), t(ex), t(ey));
        }

        #region internal method use int parameters

        bool _CheckSpareSpace(int cx, int cy, int r)
        {
            return !_CheckGrid(cx, cy, r);
        }

        int _GetInGrid(int x, int y)
        {
            return grid[x, y];
        }

        void _SetInGrid(int x, int y, int v)
        {
            grid[x, y] = v;
        }

        void _SetGrid(int cx, int cy, int r)
        {
            if (r <= 0)
                return;

            ForGrids(cx, cy, r, (x, y) =>
            {
                if (x < 0 || x >= w || y < 0 || y >= h)
                    return;

                _SetInGrid(x, y, _GetInGrid(x, y) + 1);
            });
        }

        void _UnsetGrid(int cx, int cy, int r)
        {
            if (r <= 0)
                return;

            ForGrids(cx, cy, r, (x, y) =>
            {
                if (x < 0 || x >= w || y < 0 || y >= h)
                    return;

                _SetInGrid(x, y, _GetInGrid(x, y) - 1);
            });
        }

        void _SetLine(int sx, int sy, int ex, int ey)
        {
            var dx = ex - sx;
            var dy = ey - sy;
            if (Math.Abs(dx) > Math.Abs(dy))
            {
                var dx2dy = (float)dy / dx;
                FC.ForFromTo(0, dx, (i) =>
                {
                    var x = sx + i;
                    var y = sy + (int)(i * dx2dy);
                    var v = _GetInGrid(x, y) + 1;
                    _SetInGrid(x, y, v);
                });
            }
            else
            {
                var dy2dx = (float)dx / dy;
                FC.ForFromTo(0, dy, (i) =>
                {
                    var x = sx + (int)(i * dy2dx);
                    var y = sy + i;
                    var v = _GetInGrid(x, y) + 1;
                    _SetInGrid(x, y, v);
                });
            }
        }

        bool _CheckGrid(int cx, int cy, int r)
        {
            if (r <= 0)
                return false;

            var occupied = false;
            ForGrids(cx, cy, r, (x, y) =>
            {
                if (x < 0 || x >= w
                    || y < 0 || y >= h)
                    occupied = true;
                else
                    occupied = _GetInGrid(x, y) > 0;
            }, () => !occupied);

            return occupied;
        }

        void ForGrids(int px, int py, int r, Action<int, int> fun, Func<bool> continueCondition = null)
        {
            var startX = px - r;
            var endX = px + r;
            var startY = py - r;
            var endY = py + r;
            FC.For2(startX, endX, startY, endY, (x, y) =>
            {
                fun(x, y);
            }, continueCondition);
        }

        #endregion
    }
}